/*
	Copyright (c) 2010 <copyright holders>
	This source code is licensed under The MIT License,
	see the file LICENSE for details.
	*/

#include "SimNetLogic.hpp"

#include <cassert>

#include "Faction.hpp"
#include "gen/netmsg.pb.h"
#include "MessageFile.hpp"
#include "Spatial.hpp"
#include "World.hpp"
#include "WorldManager.hpp"

namespace spelprog2 {
namespace sim {
namespace net_logic {

const float MAX_FRAME_LAG_TIME = 3.0f; //s
const unsigned MAX_FRAME_LAG_COUNT = unsigned(MAX_FRAME_LAG_TIME * float(NET_FRAME_RATE) + 0.5f);


bool WorldNetListener::getPlayerId(const net::Net & net, net::UserId userId, Int & playerId)
{
    try {
        playerId = (WideInt)boost::lexical_cast<google::protobuf::uint32>(net.getVars().getVar(userId, "player"));;
    }
    catch (const boost::bad_lexical_cast &) {
        return false;
    }

    if (playerId >= Int(0) && playerId < worldMan_.getWorld().getNumFactions()) {
        return true;
    }

    return false;
}


// MessageQueuer ==============================================================

MessageQueuer::MessageQueuer(WorldManager & worldMan) :
        WorldNetListener(worldMan),
    queue_()
{
}

void MessageQueuer::onMessage(MessagePtr msg, const net::NetMessageInfo & info)
{
    if (info.origin == net::SERVER_ID && worldMan_.getReplayMode() == REPLAYMODE_RECORDING) {
         worldMan_.getRecordingReplay() << msg;
    }

    if (info.origin == net::SERVER_ID && info.net->isServer()) {
        //we should immediately execute to keep game world updated for requests to be verified at the correct state
        worldMan_.postMsg(msg, info);
    }
    else {
        queue_.push_back(std::make_pair(msg, info));
    }

}

void MessageQueuer::processUpdates()
{
    //ho hum trust that this is in fact a client

    assert(hasUpdateMessage());

    for(;;) { //assumes there always is an update message if netUpdate has been called
        MessageQueueEntry msgEntry = queue_.front();
        queue_.pop_front();

        if (msgEntry.first->GetDescriptor() == netmsg::FrameUpdate::descriptor()) {
            msgEntry.second.net->sendToUser(*msgEntry.first.get(), net::SERVER_ID); //send reply frame
            break;
        }

        net::netlog << "Processing update message " + msgEntry.first->GetTypeName();

        worldMan_.postMsg(msgEntry.first, msgEntry.second);
    }
}

void MessageQueuer::processRequests()
{
    //ho hum trust that this is in fact a server

    while (!queue_.empty()) {
        MessageQueueEntry msgEntry = queue_.front();
        queue_.pop_front();

        net::netlog << "Processing request message " + msgEntry.first->GetTypeName();

        worldMan_.postMsg(msgEntry.first, msgEntry.second);
    }
}

bool MessageQueuer::hasUpdateMessage() const
{
    for (MessageQueue::const_iterator it = queue_.begin(), end = queue_.end(); it != end; ++it) {
        if (it->first->GetDescriptor() == netmsg::FrameUpdate::descriptor()) {
            return true;
        }
    }
    return false;
}

void MessageQueuer::loadReplayFrames(file::MessageInputFile & replayFile, net::Net & net)
{
    net::NetMessageInfo info = { &net, net::SERVER_ID };

    while (!replayFile.finished()) {
        gamelog << "loading msg";
        MessagePtr msg;
        replayFile >> msg;
        gamelog << "message loaded of type " + msg->GetTypeName();
        queue_.push_back(MessageQueueEntry(msg, info));

        if (msg->GetDescriptor() == netmsg::FrameUpdate::descriptor()) {
            gamelog << "breaking here";
            break;
        }
    }
}


// HandleFrameUpdate ==========================================================

HandleFrameUpdate::HandleFrameUpdate(WorldManager & worldMan, net::Net & net) :
        WorldNetListener(worldMan),
    net_(net),
    reachedFrames_(),
    queuer_(MessageQueuer::Pointer(new MessageQueuer(worldMan_)))
{
    reset();
}

void HandleFrameUpdate::onMessage(MessagePtr msg, const net::NetMessageInfo & info)
{
    assert(info.net == &net_);

    const netmsg::FrameUpdate * const frameUpdate = dynamic_cast<const netmsg::FrameUpdate *>(msg.get());

    if (info.origin == net::SERVER_ID) {
        if (worldMan_.getReplayMode() == REPLAYMODE_RECORDING) {
            worldMan_.getRecordingReplay() << msg;
        }

        if (!net_.isServer()) {
            queuer_->onMessage(msg, info);
        }
    }
    else {
        reachedFrames_[info.origin] = frameUpdate->sequencenumber();
    }
}

bool HandleFrameUpdate::maySimUpdate() const
{
    if (simUpdateInNetCount_ >= unsigned(SIMULATION_FRAMES_PER_NET_FRAME)) {
        return false;
    }

    float whereToBe = float(simUpdateInNetCount_) / float(SIMULATION_FRAMES_PER_NET_FRAME);
    whereToBe /= float(NET_FRAME_RATE);
    //whereToBe /= getCatchUpFactor(); //TODO: estimate how fast we must run to catch up with host, should be related to net frame catchup
    
    return netUpdateTime_ > whereToBe;
}

bool HandleFrameUpdate::mayNetUpdate() const
{
    //1) we must obviously have done enough simulation frames this network frame
    if (simUpdateInNetCount_ < unsigned(SIMULATION_FRAMES_PER_NET_FRAME)) {
        return false;
    }

    //2) check network update time
    //the idea is that we should try to update a bit more often than the theoretical rate
    //in case we are lagging behind (update messages will arrive at a rate <= theoretical rate anyway)
    if (netUpdateTime_ < 0.9f / float(sim::NET_FRAME_RATE)) {
        return false;
    }

    //3) make sure there is at least one update message in update queue
    return queuer_->hasUpdateMessage();
}

void HandleFrameUpdate::reset()
{
    reachedFrames_.clear();
    queuer_->clear();

    frameCounter_ = 0;
    netUpdateTime_ = 0.0f;
    simUpdateInNetCount_ = 0;
}

bool HandleFrameUpdate::hasLaggers() const
{
    unsigned oldestFrameAck = frameCounter_;
    for (ReachedFrames::const_iterator it = reachedFrames_.begin(), end = reachedFrames_.end(); it != end; ++it) {
        oldestFrameAck = std::min(oldestFrameAck, it->second);
    }

    unsigned frameLag = frameCounter_ - oldestFrameAck;

    return frameLag > MAX_FRAME_LAG_COUNT;
}

void HandleFrameUpdate::run(float timeDelta)
{
    netUpdateTime_ += timeDelta;

    if (net_.isServer() && worldMan_.getReplayMode() != REPLAYMODE_PLAYBACK) { //server
        const bool finishedNetFrame = netUpdateTime_ >= 1.0f / float(NET_FRAME_RATE);
        const bool hadEnoughSimFrames = simUpdateInNetCount_ >= unsigned(SIMULATION_FRAMES_PER_NET_FRAME);

        if (finishedNetFrame && hadEnoughSimFrames && !hasLaggers()) {
            queuer_->processRequests();

            //issue t3h holy update message
            netmsg::FrameUpdate frameUpdate;
            frameUpdate.set_sequencenumber(++frameCounter_);
            net_.sendToAll(frameUpdate);

            simUpdateInNetCount_ = 0;
            netUpdateTime_ = 0.0f;
        }
    }
    //client implied now (!isServer())
    else if (mayNetUpdate()) {
        queuer_->processUpdates();

        simUpdateInNetCount_ = 0;
        netUpdateTime_ = 0.0f;

        if (worldMan_.getReplayMode() == REPLAYMODE_PLAYBACK) {
            queuer_->loadReplayFrames(worldMan_.getSourceReplay(), net_);
        }
    }

    //both client and server
    if (maySimUpdate()) {
        simUpdateInNetCount_ += 1;
        worldMan_.simUpdate();
    }
}


// HandleMoveSquad ============================================================

void HandleMoveSquad::onMessage(MessagePtr msg, const net::NetMessageInfo & info)
{
    netmsg::MoveSquad * const moveSquad = dynamic_cast<netmsg::MoveSquad *>(msg.get());

    if (info.origin == net::SERVER_ID) {
        const Int playerId = Int((WideInt)moveSquad->squad().playerid());
        const Int squadId = Int((WideInt)moveSquad->squad().squadid());
        const Int2d dest(moveSquad->destination().x(), moveSquad->destination().y());
        Squad::Pointer squad = worldMan_.getWorld().getFaction(playerId).getSquad(squadId);

        squad->queueOrder(SquadOrder::Pointer(new MoveSquadOrder(*squad.get(), dest)), true);
    }
    else {
        Int playerId;
        if (!getPlayerId(*info.net, info.origin, playerId)) {
            return;
        }
        if (!worldMan_.getWorld().getFaction(playerId).hasSquad(Int((WideInt)moveSquad->squad().squadid()))) {
            return;
        }

        moveSquad->mutable_squad()->set_playerid(playerId);
        info.net->sendToAll(*moveSquad);
    }
}


// HandleChangeFormation ======================================================

void HandleChangeFormation::onMessage(MessagePtr msg, const net::NetMessageInfo & info)
{
    netmsg::ChangeFormation * const changeFormation = dynamic_cast<netmsg::ChangeFormation *>(msg.get());

    if (info.origin == net::SERVER_ID) {
        const Int playerId = Int((WideInt)changeFormation->squad().playerid());
        const Int squadId = Int((WideInt)changeFormation->squad().squadid());
        const auxmsg::Formation formation = changeFormation->newformation();
        Squad::Pointer squad = worldMan_.getWorld().getFaction(playerId).getSquad(squadId);

        squad->queueOrder(SquadOrder::Pointer(new ChangeFormationSquadOrder(*squad.get(), formation)), true);
    }
    else {
        Int playerId;
        if (!getPlayerId(*info.net, info.origin, playerId)) {
            return;
        }
        if (!worldMan_.getWorld().getFaction(playerId).hasSquad(Int((WideInt)changeFormation->squad().squadid()))) {
            return;
        }

        changeFormation->mutable_squad()->set_playerid(playerId);
        info.net->sendToAll(*changeFormation);

    }
}


// HandleStopSquad ============================================================

void HandleStopSquad::onMessage(MessagePtr msg, const net::NetMessageInfo & info)
{
    netmsg::StopSquad * const stopSquad = dynamic_cast<netmsg::StopSquad *>(msg.get());

    if (info.origin == net::SERVER_ID) {
        const Int playerId = Int((WideInt)stopSquad->squad().playerid());
        const Int squadId = Int((WideInt)stopSquad->squad().squadid());
        Squad::Pointer squad = worldMan_.getWorld().getFaction(playerId).getSquad(squadId);

        squad->queueOrder(SquadOrder::Pointer(new StopSquadOrder(*squad.get())), true);
    }
    else {
        Int playerId;
        if (!getPlayerId(*info.net, info.origin, playerId)) {
            return;
        }
        if (!worldMan_.getWorld().getFaction(playerId).hasSquad(Int((WideInt)stopSquad->squad().squadid()))) {
            return;
        }

        stopSquad->mutable_squad()->set_playerid(playerId);
        info.net->sendToAll(*stopSquad);
    }
}


}; //net_logic
}; //sim
}; //spelprog2
